<?php
/**
 * clase_zen_POA_Compilador.php
 * @author Juan Belon
 * @access public
 * @copyright LGPL, GPL
 * @package zen_POA
 * @version 0.1.1
 * @uses zenphp FrameWork
 * @link http://csl2-zenphp.forja.rediris.es
 * @link http://www.zenphp.es
 * @link https://forja.rediris.es/projects/csl2-zenphp/
 * @magic 
 * TODO: Completar la Documentación de las clases de POA
 */
// +----------------------------------------------------------------------
// | zenphp.es
// +----------------------------------------------------------------------
require_once "clase_zen_POA_AnalizadorSintactico.php";
require_once "clase_zen_POA_PicaCodigo.php";
require_once "clase_zen_POA_Anotador.php";

class zen_POA_Compilador
{
    /**
    * @desc Codigo fuente
    * @var str
    */
    var $codigo;
    /**
    * @desc Analizador Sintáctico de Código
    * @var zen_POA_AnalizadorSintactico
    */
	var $analizador;
	/**
    * @desc Pila de definiciones
    * @var array
    */
	var $pilaDefs;
    /**
    * @desc Puntos de corte 
    * @see zen_POA_PuntosCorte
    * @var array
    */
	var $puntos_corte;
    /**
    * @desc La clase tejedora de código
    * @var zen_POA_Tejedora
    */
	var $tejedora;
    /**
    * @desc Bandera para picar o no código con la clase zen_POA_PicaCodigo xD
    * @var bool
    */
	var $compacto;
	/**
    * @desc Se ha devuelto a la pila un token?
    */
	var $devuelto;

    /**
    * @desc Constructor
    * @param str $cadCodigo
    * @param zen_POA_Tejedora  $tejedora
    * @return zen_POA_Compilador
    */
    function zen_POA_Compilador($cadCodigo, $tejedora)
    {
    	$this->pilaDefs = array();
    	$this->puntos_corte = array();
    	$this->analizador = null;

		// Guardar codigo fuente inicial
		$this->codigo = $cadCodigo;

		// Guardar la lista de aspectos
		$this->tejedora = $tejedora;

		// Define el modo compacto
		$this->compacto = true;
		
		// Define el valor devuelto como falso
		$this->devuelto = false;
    }
    
    
    function obtenerCodigo()
    {
    	return $this->codigo;
    }


	function compilar($compacto = true)
	{
		// Modo compacto?
		$this->compacto = (bool) $compacto;
		
		// Corregir paréntesis perdidos
		$this->compilarParentesisPerdidos();

		// Pre-compilar los puntos de corte definidos por el usuario:
		$this->compilarPuntosCorte_Propios();

		// Corregir paréntesis perdidos,ahora sólo quedan los Puntos de Corte Automáticos
		$this->compilarParentesisPerdidos();

        // compilar Puntos de Corte Automáticos
		$this->compilarPuntosCorte_Automaticos();

		// Recordar que algunas versiones de PHP quitan el token de cierre de PHP...
		if (substr($this->codigo, strlen($this->codigo) - 2, 2) != "?>") {
			$this->codigo .= "?>";
		}
	}
	

	function compilarPuntosCorte_Propios()
	{
    	// Crear el analizador
		$this->analizador = new zen_POA_AnalizadorSintactico($this->codigo);
		$this->analizador->ponerIndice(0);

        // Inicializar resultado
		$resultado = "";

        // Iterar cada token PHP
		while (($tok = $this->analizador->tokenActual()) !== null) {
			$resultado .= $this->analizarTokenPropio($tok);
			$this->analizador->siguienteToken();
		}

		// Asignar propiedades
		$this->codigo = $resultado;
		$this->analizador = null;
	}
	
	
	function analizarTokenPropio($token)
	{
		$resultado = "";

        if (!is_array($token)) {
			$resultado .= $token;

			switch ($token) {
				case "{":
					// Incluir la llave de apertura en la pila
					array_push($this->pilaDefs, "{");

					break;

				case "}":
					// Procesar sólo si hay al menos una función o una clase definida
					if (count($this->pilaDefs) >= 2) {
						// Devolver la posible llave de apertura y el nombre del método:
						$llave = array_pop($this->pilaDefs);
						$metodo = array_pop($this->pilaDefs);

						if ($llave == "{" && ($metodo == "{" || $metodo == "}")) {
							array_push($this->pilaDefs, $metodo);
						}
					} else {
						// Borrar la definición de la clase y/o basura en la memoria
						$this->pilaDefs = array();
					}

					break;
			}
        } else {
        	switch (token_name((int) $token[0])) {
				case "T_CLASS":
				case "T_FUNCTION":
					$resultado .= $this->compilarClase_o_Metodo_Propio($token);
					break;

	            case "T_COMMENT":
	        		$resultado .= $this->compilarComentarioPropio($token);
					break;

	            default:
	            	// Añadir token a la cadena resultado
					$resultado .= $token[1];
					break;
	        }
        }

		return $resultado;
	}

	
	function compilarClase_o_Metodo_Propio($token)
	{
        // Añadir token a la cadena resultado
		$resultado = $token[1];
		//print_r($token);
		$tk = $this->analizador->siguienteToken();

        // Encontrar el siguiente token analizable
        while(!is_array($tk) || (is_array($tk) && token_name((int) $tk[0]) != "T_STRING")) {
            // T_WHITESPACE o &
    		$tk = $this->analizador->siguienteToken();
        }
        
        // T_STRING
        $siguienteToken = $this->analizador->tokenActual();
		
		// Incluir el nombre la clase/función a la pila!
		array_push($this->pilaDefs, $siguienteToken[1]);

		$tk = $this->analizador->tokenActual();

        // Volver al último token analizable:
        while(!is_array($tk) || (is_array($tk) && (token_name((int) $tk[0]) != "T_CLASS" && token_name((int) $tk[0]) != "T_FUNCTION"))) {
            // En otro caso es: T_CLASS / T_FUNCTION
    		$tk = $this->analizador->anteriorToken();
        }
		
		return $resultado;
	}
	
	
	function compilarComentarioPropio($token)
	{
		$resultado = "";

		// Solo se procesa si hay al menos una función una clase definida/s
		if (count($this->pilaDefs) >= 2) {
			preg_match_all("/\/\/\/\s*punto_corte\s*:\s*([^\r\n]*)/i", $token[1], $punto_corte, PREG_OFFSET_CAPTURE);

            // Algún punto de corte ?
			if (is_array($punto_corte) && count($punto_corte) > 0 && count($punto_corte[0]) > 0) {
				// Obtener nombre de la clase
				$clase = array_shift($this->pilaDefs);
				
				// Buscar nombre del método:
				$metodo = "";

				for ($i = count($this->pilaDefs) - 1; $i >= 0; $i--) {
					if ($this->pilaDefs[$i] != "{" && $this->pilaDefs[$i] != "}") {
						$metodo = $this->pilaDefs[$i];
						break;
					}
				}

				// Leer el código de notación
				$anotador = & $this->obtenerAnotadorDePuntoCortePropio($clase, $metodo, $punto_corte[1][0][0]);
				$resultado .= $anotador->obtenerContenido();// . "\r\n";

				// Poner el nombre de nuevo en la pila:
				array_unshift($this->pilaDefs, $clase);
			} else {
				// añadir toquen al resultado:
				$resultado .= $token[1];
			}
		} else {
			// añadir toquen al resultado:
			$resultado .= $token[1];
		}
		
		return $resultado;
	}
	
	
	function compilarParentesisPerdidos()
	{
		// Se arrega con una expresión regular que va insertando los paréntesis perdidos(solo encuentra 2)
	    $this->codigo = preg_replace("/(if|for|while|switch)\s*(\([^()]*(\([^()]*\)[^()]*)*\))([^{;]*;)/i", "\\1 \\2 {\\4 }", $this->codigo);

		// TODO: Añadir resto de condiciones para el else
	    $this->codigo = preg_replace("/(else)\s*([^{;]*;)/i", "\\1 {\\2 }", $this->codigo);
	}

	
	function compilarPuntosCorte_Automaticos()
	{
    	// Crear Analizador de Código:
		$this->analizador = new zen_POA_AnalizadorSintactico($this->codigo);
		$this->analizador->ponerIndice(0);
		
		// Resultados en blanco de nuevo
		$resultado = "";

		// Iterar cada token de PHP
		while (($tok = $this->analizador->tokenActual()) !== null) {
			$resultado .= $this->analizarTokenAuto($tok);
			$this->analizador->siguienteToken();
		}

		// Asignar propiedades
		$this->codigo = $resultado;
		$this->analizador = null;
	}
	
	
	function analizarTokenAuto($token)
	{
		$resultado = "";

        if (!is_array($token)) {
        	switch ($token) {
				case "{":
					$resultado .= $this->compilarLlavesAbiertas();
					break;

				case "}":
                    $resultado .= $this->compilarLlavesCerradas();
					break;

				default:
					$resultado .= $token;
					break;
			}
        } else {
        	switch (token_name((int) $token[0])) {
				case "T_CLASS":
				case "T_FUNCTION":
					$resultado .= $this->compilarClase_o_Metodo_Auto($token);
					break;

				case "T_EXIT":
				case "T_RETURN":
					$resultado .= $this->compilarRetorno_o_Salida_Auto($token);
					break;

	            default:
	            	// Añadir token a la cadena resultado
					$resultado .= $token[1];
					break;
	        }
        }
		return $resultado;
	}
	
	
	function compilarLlavesAbiertas()
	{
		// Añadir token a la cadena resultante
		$resultado = "{";

		// Procesar solo si hay al menos una función o clase definida
		if (count($this->pilaDefs) >= 1) {
			// Recuperar los posibles nombres de método
			$metodo = array_pop($this->pilaDefs);

            // Comprobar por definiciones dentro de la llave. Si el último token definido
			// no es un nombre de método, no se usa
			if (is_array($metodo)) {
				// Recuperar el nombre de clase:
				$clase = array_shift($this->pilaDefs);

				// Comprobar si es un nombre de función:
				if (($clase === null || (is_array($clase) && $clase[0] === "class")) && $metodo[0] !== "class") {
					// Leer el código anotado:
					$anotador = & $this->obtenerAnotadorDePuntoCorteAuto($clase[1], $metodo[1], "antes");
					$resultado .= " " . $anotador->obtenerContenido();
				}

				// Si es un método ($clase contiene un token 'clase'), se devuelve a la pila!
				if ($clase !== null) {
					// Poner el nombre de la clase en la pila
					array_unshift($this->pilaDefs, $clase);
				}
			}

			// Poner el método, la llave o cualquier otro token de vuelta a la pila
			array_push($this->pilaDefs, $metodo);
		}

		// Incluir llave abierta en la pila
		array_push($this->pilaDefs, "{");

		return $resultado;
	}
	
	 /**
     * @desc Copiado de compilarLlavesAbiertas 
     */
	function compilarLlavesCerradas()
	{
		$resultado = "";

		// Procesar sólo si hay al menos una función o una clase definida
		if (count($this->pilaDefs) >= 1) {
			// Recuperar los posibles nombres de método     
			$llave = array_pop($this->pilaDefs);
			$metodo = array_pop($this->pilaDefs);

			// Hay ya algún método definido?
			if (!is_array($llave) && $llave == "{" && is_array($metodo)) {
				// Recuperar el nombre de la clase:
				$clase = array_shift($this->pilaDefs);

                // Comprobar si es una función o un método
				if (($clase === null || (is_array($clase) && $clase[0] === "class")) && 
					$metodo[0] !== "class" && $this->devuelto === false) {
					// Cargar el código de la anotación asociada:
					$anotador = & $this->obtenerAnotadorDePuntoCorteAuto($clase[1], $metodo[1], "despues");
                    $resultado .= $anotador->obtenerContenido();
				}

                // Si es un método ($clase contiene un token 'clase'),devolverlo a la pila
                if ($clase !== null) {
					// Devolver a la pila!:
					array_unshift($this->pilaDefs, $clase);
				}
				
				$this->devuelto = false;
			} elseif ((!is_array($llave) && $llave == "{") && (!is_array($metodo) && ($metodo == "{" || $metodo == "}"))) {
				// Put the function, curly or other token back to the stack
				array_push($this->pilaDefs, $metodo);
			}
		} else {
			// Remove class definition and/or garbage
			$this->pilaDefs = array();
		}

        // Append token into result string
		return $resultado . "}";
	}
	
	
	function compilarClase_o_Metodo_Auto($token)
	{
		// Añadir token al resultado
		$resultado = $token[1];

        $tk = $this->analizador->siguienteToken();

        // Encontrar el siguiente token analizable
        while(!is_array($tk) || (is_array($tk) && token_name((int) $tk[0]) != "T_STRING")) {
            // T_WHITESPACE o &
    		$tk = $this->analizador->siguienteToken();
        }
        
        // T_STRING
        $siguienteToken = $this->analizador->tokenActual();

        // Incluir el nombre de la clase/función name en la pila
		array_push($this->pilaDefs, array($token[1], $siguienteToken[1]));

        $tk = $this->analizador->tokenActual();

        // Devolver el último token analizable
        while(!is_array($tk) || (is_array($tk) && (token_name((int) $tk[0]) != "T_CLASS" && token_name((int) $tk[0]) != "T_FUNCTION"))) {
            // En cualquier otro caso: T_CLASS / T_FUNCTION
    		$tk = $this->analizador->anteriorToken();
        }

		return $resultado;
	}
	
	
	function compilarRetorno_o_Salida_Auto($token)
	{
		$resultado = "";

		// Encontrar el nombre de la clase
		$clase = array_shift($this->pilaDefs);

		// Para buscar el método
		$metodo = "";

		for ($i = count($this->pilaDefs) - 1; $i >= 0; $i--) {
			if (is_array($this->pilaDefs[$i])) {
				$metodo = $this->pilaDefs[$i];
				break;
			}
		}

		// Cargar el código definido por el usuario
		$anotador = & $this->obtenerAnotadorDePuntoCorteAuto($clase[1], $metodo[1], "despues");
		$codigo = $anotador->obtenerContenido();

		// añadir espacio si se ha definido código
    	if (strlen($codigo) > 0) {
			$resultado .= $codigo . " ";
		}

		// Devolver el nombre de token:
		$tokenName = token_name((int) $token[0]);

		// Añadir el token en la cadena
		$resultado .= ($tokenName == "T_EXIT") ? "exit" : "return";
		
		// Devolver el nombre a la pila
		array_unshift($this->pilaDefs, $clase);

		// Comprobar si la última sentencia es un return (inclusion de la anotación después del último comando: return)
		if ((is_array($clase) && $clase[0] === "class" && count($this->pilaDefs) < 5) || count($this->pilaDefs) < 3) {
        	$this->devuelto = true;
		}
		return $resultado;
	}

	
	function &obtenerAnotadorDePuntoCortePropio($clase, $metodo, $nombre_puntocorte)
	{
		$anotador = & new zen_POA_Anotador();

		$a = & $this->tejedora->obtenerAnotadorDePuntoCortePropio($clase, $metodo, $nombre_puntocorte);
		$codigo = $a->obtenerContenido();

		// Hay codigo que reemplazar?
		if (strlen($codigo) > 0) {
			// Hay que picar codigo? XD
			if ($this->compacto) {
				$codigo = zen_POA_PicaCodigo::procesar($codigo);
			} else {
				$codigo = "\r\n" . $codigo . "\r\n";
			}

			// Añadir un texto informativo
			$codigo = "
            /* zenphp::generador \"" . $nombre_puntocorte . "\" Codigo */ " . $codigo . " ";
		}
		
		$anotador->insertarContenido($codigo);
		
		return $anotador;
	}
	
	
	function & obtenerAnotadorDePuntoCorteAuto($clase, $metodo, $nombre_puntocorte_auto)
	{
		$anotador = & new zen_POA_Anotador();

		$a = & $this->tejedora->obtenerAnotadorDePuntoCorteAuto($clase, $metodo, $nombre_puntocorte_auto);
		$codigo = $a->obtenerContenido();

		// Reemplazos?
		if (strlen($codigo) > 0) {
			// El Pica código nos ahorra todo el trabajo
			if ($this->compacto) {
				$codigo = zen_POA_PicaCodigo::procesar($codigo);
			} else {
				$codigo = "\r\n" . $codigo . "\r\n";
			}

			// Añadir un texto informativo
			$codigo = "
            /* zenphp::generador \"". $nombre_puntocorte_auto . "\" Codigo Auto */ " . $codigo . " ";
		}
		
		$anotador->insertarContenido($codigo);
		
		return $anotador;
	}
}


?>